//
//  FreebaseCollection.m
//  Freebase
//
//  Created by Alec Flett on 10/15/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

// For NSIndexPath
#import <UIKit/UIKit.h>

#import "FreebaseCollection.h"
#import "JSON/JSON.h"

@implementation FreebaseCollection

@synthesize fbs;
@synthesize pageData = _pageData;
@synthesize cursors = _cursors;
@synthesize pendingCursors = _pendingCursors;
@synthesize query = _query;
@synthesize pageSize = _pageSize;

- (FreebaseCollection*)initWithDelegate:(id)delegate
{
    if ((self = [super init])) {
        _item_count = -1; // not loaded
        _delegate = delegate;
        if (!fbs)
            fbs = [FreebaseSession session];
    }
    
    return self;
}

- (BOOL)dataLoaded
{
    return _item_count != -1;
}

- (NSInteger)rowsInSection:(NSInteger)section {
// default assumes one section (?)
#pragma unused(section)

    if (![self dataLoaded]) {
        // count not loaded
        if (_pageData && [_pageData count]>0)
            return [[_pageData objectAtIndex:0] count];
    } else  {
        return _item_count;
    }
    
    // no item data, no item count
    return 0;
    
}

- (id <FreebaseRequest>) loadDataInPage:(NSUInteger)page
{
    id cursor = [_cursors objectAtIndex:page];
    if (!cursor || cursor == [NSNull null]) {
        NSLog(@"Couldn't load page %d because there was no cursor", page);
        if (page > 0)
            [self loadDataInPage:page-1];
        return nil;
    }
    
    if ([_pendingCursors member:cursor])
        return nil;
    
    NSLog(@"%@: Loading page %d using %@", self, page, fbs);
    
    // embed the page number in the query name...ugly.
    NSString* name = [NSString stringWithFormat:@"item_list_%d", page];
    id <FreebaseRequest> req = [fbs mqlread:_query 
                             name:name
                         delegate:self
                didFinishSelector:@selector(pageLoaded:name:cursor:)
                          options:[NSDictionary dictionaryWithObject:cursor
                                                              forKey:@"cursor"]];
    [_pendingCursors addObject:cursor];
    return req;
}

- (NSInteger)pageStart: (NSInteger)page
{
    NSNumber *pageStartNumber = [_pageStartIndexes objectAtIndex:page];
    if (pageStartNumber == (NSNumber*)[NSNull null]) {
        if (page > 0)
            pageStartNumber = [_pageStartIndexes objectAtIndex:page-1];
        else
            return -1;
    }
            
    return [pageStartNumber integerValue];
}

- (id <FreebaseRequest>) loadItemsFromQuery: (id)q {
    // if q is nil, ignore
    if (q)
        self.query = q;
    
    if ([_query isKindOfClass:[NSString class]]) {
        NSString *queryStr = (NSString*)_query;
        self.query = [queryStr JSONValue];
    }
    
    self.pendingCursors = [[NSMutableSet alloc] init];
    
    self.cursors = [NSMutableArray arrayWithCapacity:2];
    
    
    [_cursors addObject:[NSNumber numberWithBool:YES]];
    
    self.pageData = [NSMutableArray arrayWithCapacity:2];
    _pageStartIndexes = [[NSMutableArray arrayWithCapacity:2] retain];
    
    _item_count = -1;
    
    id<FreebaseRequest> req = [self loadDataInPage:0];
    
    // now load up the count - copy the array
    // and alter it into a counting query
    NSMutableDictionary *dict = [NSMutableDictionary 
                                 dictionaryWithDictionary:(NSDictionary*)[_query objectAtIndex:0]];
    [dict setValue:@"count" forKey:@"return"];
    
    // don't bother sorting if we just want the number!
    [dict removeObjectForKey:@"sort"];
    
    _pageSize = 100;
    // extract page size from the query limit, if any.
    NSNumber* v = [dict objectForKey:@"limit"];
    if (v)
        _pageSize = [v intValue];
    if (!_pageSize)
        _pageSize = 100;
    
    [fbs mqlread:dict name:@"item_count" 
        delegate:self
didFinishSelector:@selector(countLoaded:)
         options:[NSDictionary dictionary] ];
    
    return req;
}

- (void)dealloc {    
    [super dealloc];
    [_pageData release];
    [_pageStartIndexes release];
    [_cursors release];
    [fbs release];
    
}

- (NSInteger)rowCount{
    if (_item_count == -1) {
        // count not loaded, so just add up the first page
        if (_pageData && [_pageData count]>0)
            return [[_pageData objectAtIndex:0] count];
    } else  {
        return _item_count;
    }
    
    // no item data, no item count
    return 0;
    
}

// make sure all the appropriate lists are created given the information we know.
- (void)createLists:(NSInteger)items
{
    if (items == -1) {
        // pretend we have exactly one page until the item_count loads
        items = _pageSize + 1;
    } else {
        _item_count = items; 
    }
    
    int num_arrays = (items / _pageSize) + 1;
    int create_cursors = num_arrays - [_cursors count];        
    int create_list_lists = num_arrays - [_pageData count];
    
    NSNull *null = [NSNull null];
    for (int i=0; i<create_cursors; i++) {
        [_cursors addObject:null];
    }

    // add one dummy at the end, this makes the first 
    // loop in itemForIndex correctly 
    // overshoot the last page, so that it can rewind
    for (int i=0; i<create_list_lists+1; i++) {
        [_pageData addObject:null];
        [_pageStartIndexes addObject:null];
    }
    [_pageStartIndexes replaceObjectAtIndex:0 withObject:[NSNumber numberWithInt:0]];
    
}

- (void)countLoaded:(id)data
{
    if (_item_count == -1) {
        NSNumber *num = data;
        int items = [num intValue];
        NSLog(@"item_count loaded, %d items to %@", items, _delegate);
        [self createLists:items];
        _item_count = items;
        
    } 
    
    else 
    {
        NSLog(@"Got 2nd item_count, already had %d", _item_count);
    }
    [_delegate itemTotalLoaded:_item_count];
}

- (id)itemAtIndex:(NSUInteger)index
{
    NSInteger itemPage = 0;       // page number this item is on
    NSUInteger itemPageOffset = index; // the offset in that page
    NSInteger prevStartIndex = 0; // the previous one through the loop, 
                                  // in case we need to rewind
    
    //NSLog(@"Looking for item %d", index);
    for (NSNumber *pageStartIndexNumber in _pageStartIndexes) {
        // it was maybe on the previous page?
        //NSLog(@"  ..checking page %d", itemPage);
        
        // this should never be true for the first entry, 
        // that's why it's safe to decrement itemPage
        if (pageStartIndexNumber == (NSNumber*)[NSNull null]) {
            //NSLog(@"    ..oops, page %d has no startindex, going back to the previous page",
            //      itemPage);
            itemPageOffset = index - prevStartIndex;
            itemPage--;
            break;
        }
        
        //NSLog(@"  ..There are %d items on the current page..", [data count]);
        // if we're on this page, this will be our offset
        NSUInteger pageStartIndex = [pageStartIndexNumber integerValue];
        itemPageOffset = index - pageStartIndex;
        
        // now check if the next page only has items past 'index'
        //NSLog(@"  .. comparing %d > %d = %d?", pageStartIndex, index, pageStartIndex > index);
        if (pageStartIndex > index) {
            // ok, so we went too far
            itemPageOffset = index - prevStartIndex;
            itemPage--;
            //NSLog(@"    .. oops, went past our index, we must be on the previous page?!");
            break;
        }
        
        // keep our loop variables in sync at the end here
        itemPage++;
        prevStartIndex = pageStartIndex;
    }
    //NSLog(@"done at page %d offset %d (%d indexes: %@)", itemPage, itemPageOffset, [_pageStartIndexes count], _pageStartIndexes);
    
    NSArray *itemList = [_pageData objectAtIndex:itemPage];
    if ((NSObject*)itemList != [NSNull null]) {
        // data is loaded
        if (itemPageOffset < [itemList count]){
            NSDictionary *item = [itemList objectAtIndex:itemPageOffset];
            return item;            
        }
        else
            // a little wonky, maybe fall through below somehow? 
            // This means basically we're trying to load
            // "page 4, offset 325" which would really be
            // "page 7, offset 25", so we need to kick off the 
            // loading of the *next* page.
            itemPage++;
    }
    
    [self loadDataInPage:itemPage];
    
    // aggressively load 10 items ahead, in case anyone asks
//    if (_pageSize - itemListPosition < 10) {
//        NSArray* nextList = [_pageData objectAtIndex:itemListIndex+1];
//        if ((NSObject*)nextList == [NSNull null])
//            [self loadDataInPage:itemListIndex+1];
//    }
    
    return nil;
}

- (void)pageLoaded:(id)data name:(NSString*)queryName cursor:(id)cursor
{
    // this is the N from @"item_list_N"
    NSString *pageStr = [queryName substringFromIndex:10];
    
    int page = [pageStr intValue];    
    int pageStart = 0;
    if (page > 0) {
        // we'll have to start caching page lengths too for this 
        // calculation - imagine _pageData[page-1] == null
        NSArray *previousPageData = [_pageData objectAtIndex:page-1];
        NSInteger previousPageStart = [self pageStart:page-1];
        pageStart = (previousPageStart +
                     [previousPageData count]);
    }
    // just make sure the list is there.
    [self createLists:-1]; 
    
    // save both the data and where in the dataset we are
    [_pageData replaceObjectAtIndex:page withObject:data];
    if ([_pageStartIndexes count] > (NSUInteger)page+1)
        [_pageStartIndexes replaceObjectAtIndex:page+1 
                                     withObject:[NSNumber numberWithInteger:pageStart + [data count]]];
    else
        NSLog(@"Can't replaceObjectAtIndex");
    
    // remove the cursor we just loaded
    id cursorLoaded = [_cursors objectAtIndex:page];
    [_pendingCursors removeObject:cursorLoaded];
    
    // if we got a real cursor
    if (!([cursor isKindOfClass:[NSNumber class]] &&
          'c' == *[(NSNumber*)cursor objCType]))
        [_cursors replaceObjectAtIndex:page+1 withObject:cursor];
        
    [_delegate pageLoaded:page data:data];
}

- (void)errorDidOccur:(id)error request:(id<FreebaseRequest>)request
{
#pragma unused(request)

    NSLog(@"errorDidOccur: %@", error);
}


- (void)reload
{
    NSLog(@"touching..");
    [self.fbs touch:@"touch" delegate:self didFinishSelector:@selector(touchComplete)];
}

- (void)touchComplete
{
    NSLog(@"touch complete");
    [self loadItemsFromQuery:self.query];
}

@end
